<?php
/********************************************************************
 * This is a very simplified version of PsychoQuery from            *
 * PsychoStats. Do not use it. Use the PsychoQuery Class instead    *
 * (bundled with psychostats)                                       *
 ********************************************************************/

class LIVE {
	var $challenge_id;
	/////////////////////////////////// VERBINDUNG ZUM SERVER
	
	// Data
	var $data;
	
	// Constructor that initializes the database abstraction layer
	function __construct($ip, $port) {
	  return $this->init($ip, $port);
  } 
  
  function LIVE($ip, $port) {
	  return $this->init($ip, $port);
  }
  
  function init($ip, $port) {
    $proto="udp";
		$sip = gethostbyname($ip);
		$this->addy["ip"] = $sip;
		if($port == "") $port = 27015;
		$this->addy["port"] = $port;
		$this->addy["proto"] = $proto;
		return TRUE;
  }
	
	function address($ip, $port) {
		return $this->init($ip, $port);
	}
	
	function connect()
	{
		if($this->sock= @fsockopen($this->addy["proto"]."://".$this->addy["ip"], $this->addy["port"]))
		{
			stream_set_timeout($this->sock, 5);
			return TRUE;
		}
		return FALSE;
	}
	
	function _getchallenge() {
		if (!$this->challenge_id) {
			$res = $this->_sendquery('W');
			if (!$res) return $this->challenge_id;
			if ($this->raw != '') {
				$this->raw = substr($this->raw, 5);		// strip off response header bytes
				$this->challenge_id = $this->_getlong();
			}
		}
		return $this->challenge_id;
	}

	/////////////////////////////////// AUSWERTUNG DER DATEN
	
	// reads a byte from the raw input
	function _getbyte() {
		if (empty($this->raw)) return ''; //Leer?
		$byte = substr($this->raw, 0, 1);
		$this->raw = substr($this->raw, 1);
		return ord($byte);
	}

	// reads a null terminated string from the raw input
	function _getnullstr() {
		if (empty($this->raw)) return '';
		$end = strpos($this->raw, "\0");			// find position of first null byte
		$str = substr($this->raw, 0, $end);		// extract the string (excluding null byte)
		$this->raw = substr($this->raw, $end+1);		// remove the extracted string (including null byte)
		return $str;						// return our str (no null byte)
	}
	
	function _getlong() {
		if (empty($this->raw)) return '';
		$lo = $this->_getshort();
		$hi = $this->_getshort();
		$long = ($hi << 16) | $lo;
		return $long;
	}
	
	function _getshort() {
		if (empty($this->raw)) return '';
		$lo = $this->_getbyte();
		$hi = $this->_getbyte();
		$short = ($hi << 8) | $lo;
		return $short;
	}
	
	function _getfloat() {
		if (empty($this->raw)) return '';
		$f = @unpack("f1float", $this->raw);
		$this->raw = substr($this->raw, 4);
		return $f['float'];
	}
	
	// internal function to send a non-authoritative query to a halflife server (NOT RCON COMMANDS)
	function _sendquery($cmd) {
		$retry = 0;
		$oldmqr = get_magic_quotes_runtime();

		$packets = array();					// stores each packet seperately, so we can combine them afterwards
	# on some systems using 0xFFFFFFFF causes PHP to actually pack 0x7FFFFFFF and it breaks the query
		$command = pack("V", -1) . $cmd;
	#	$command = pack("V", 0xFFFFFFFF) . $cmd;
	#	$command = pack("v", 0xFFFF) . pack("v", 0xFFFF) . $cmd;
		$this->raw = "";

		if ($oldmqr) set_magic_quotes_runtime(0);
		fwrite($this->sock, $command, strlen($command));

		$expected = 0;						// # of packets we're expecting
		do {
			$packet = fread($this->sock, 1500);
			if (strlen($packet) == 0) {
				$retry++;
				fwrite($this->sock, $command, strlen($command));
				$expected = 1;
				continue;
			}

			$header = substr($packet, 0, 4);				// get the 4 byte header
			$ack = @unpack("N1split", $header);
			$split = sprintf("%u", $ack['split']);
			if ($split == 0xFeFFFFFF) {				// we need to deal with multiple packets
				$packet = substr($packet, 4);				// strip off the leading 4 bytes
				$header = substr($packet, 0, 5);			// get the 'sub-header ack'
				$packet = substr($packet, 5);				// strip off 32bit int ID, seq# and total packet#
				$info = @unpack("N1id/C1byte", $header);		// we don't really care about the ID
				if (!$expected) $expected = $info['byte'] & 0x0F;	// now we know how many packets to receive
				$seq = (int)($info['byte'] >> 4);			// get the sequence number of this packet
				$packets[$seq] = $packet;				// store the packet
				$expected--;
			} elseif ($split == 0xFFFFFFFF) {				// we're dealing with a single packet
				$packets[0] = $packet;
				$expected = 0;
			}
		} while ($expected and $retry < 2);

		if ($oldmqr) set_magic_quotes_runtime(1);
		ksort($packets, SORT_NUMERIC);
		$this->raw = implode('', $packets);				// glue the packets together to make our final data string
		return TRUE;
	}

	// reads a character from the raw input
	function _getchar() {
		return sprintf("%c", $this->_getbyte());
	}
	
	/////////////////////////////////// DATEN HOLEN
	
	function query_info() {
		$infostr = 'TSource Engine Query' . pack('x');
		$this->_sendquery($infostr);
		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////    
		if ($this->raw != '') {            //Wenn wir was vom Server bekommen haben...
			$this->data['ipport'] = $this->addy["ip"].":".$this->addy["port"];
			list($this->data['ip'], $this->data['port']) = explode(':', $this->data['ipport']);     //Alles schön sauber ablegen...	
	        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	        //Jetzt alle Daten aus den Antwortpaketen in den Array speichern
	        $this->raw                      = substr($this->raw, 5);	// strip off response header bytes
		      $this->data['protocol']			= $this->_getbyte();	    // 6
		      $this->data['name'] 			= $this->_getnullstr();
	        $this->data['map'] 			    = $this->_getnullstr();
	        $this->data['gamedir'] 			= $this->_getnullstr();
	        $this->data['gamename'] 		= $this->_getnullstr();
	        $this->data['appid']			= $this->_getbyte() | ($this->_getbyte() << 8);
	        $this->data['totalplayers']		= $this->_getbyte();
	        $this->data['maxplayers']		= $this->_getbyte();
	        $this->data['maxbots']			= $this->_getbyte();
	        $this->data['servertype']		= $this->_getchar();
	        $this->data['serveros']			= $this->_getchar();
	        $this->data['serverlocked']		= $this->_getbyte();
	        $this->data['serversecure']		= $this->_getbyte();

		   return $this->data;
		}
	    return FALSE;
	}
	
	// The sub-class must always contain this method to query the active players list
	function query_players() {
		$plrstr = 'U';
		$res = $this->_sendquery($plrstr . pack("V", $this->_getchallenge()));
		if (!$res) return FALSE;
		if (!empty($this->raw)) {
			$this->raw = substr($this->raw, 5);		// strip off response header bytes
			$this->data['activeplayers'] 	= $this->_getbyte();
			$this->data['players'] 		= array();
			for ($i=1; $i <= $this->data['activeplayers']; $i++) {
				if ($this->raw == '') break;
				$this->data['players'][] = array(
					'id'		=> $this->_getbyte(),
					'name'		=> $this->_getnullstr(),
					'kills'		=> $this->_getlong(),
					'onlinetime'	=> (int)$this->_getfloat()
				);
			}
			$this->data['activeplayers'] = count($this->data['players']);
			return $this->data;
		}
		return FALSE;
	}
	
	function query_rules() {
		$rulestr = 'V';
		$res = $this->_sendquery($rulestr . pack("V", $this->_getchallenge()));
		if (!$res) return FALSE;
		if (!empty($this->raw)) {
			$this->raw = substr($this->raw, 5);		// strip off response header bytes
			$this->data['totalrules'] = ($this->_getbyte() | ($this->_getbyte() << 8)) - 1;
			$this->data['rules'] = array();
			for ($i=1; $i <= $this->data['totalrules']; $i++) {
				$this->data['rules'][ trim($this->_getnullstr()) ] = trim($this->_getnullstr());
			}
			return $this->data;
		}
		return FALSE;
	}
	
	function get_result() {
		return $this->data;
	}
	
}

?>
